﻿#include <thread>
#include <chrono>
#include <QTimer>
#include <QDataStream>
#include <QJsonArray>
#include <QJsonObject>
#include <QJsonDocument>
#include <QLibrary>
#include <QDir>
#include <QThread>
#include <QQmlContext>
#include <QEventLoop>
#include <QFileInfo>
#include "PluginsManager.h"
#include "PluginObject.h"
#include "../Frameworks/AbstruactEventMediator.h"
#include "../Frameworks/MediatorEventDefine.h"

PluginsManager::PluginsManager(QQmlContext* context, QObject* parent /*= nullptr*/)
	: QObject(parent)
	, m_ownTopics({"logs", "sensors", "others"})
	, m_needCommands({"setWorkMode", "SimAction"})
	, PluginAction(context)
{
	if(!parent || thread() != context->thread()){
		std::throw_with_nested(std::logic_error("Must be in the same thread as the QQmlEngine thread!"));
	}
	m_processMsgMap[ME_UPDATEREGISTER] = std::bind(&PluginsManager::updateRegister, this, std::placeholders::_1);
}

PluginsManager::~PluginsManager() {
	for (auto it = std::cbegin(m_asyncThreads); it != std::cend(m_asyncThreads); it = std::next(it)) {
		if (it->second->isRunning()) {
			it->second->quit();
			it->second->wait();
		}
	}
	if(m_mediator)
		m_mediator->unRegisterAction(QStringView(u"PluginsManager"));
}

void PluginsManager::update(QJsonObject const& msg) {
	auto it = m_processMsgMap.find(msg[ME_MSG_KEY].toString());
	if(it != std::cend(m_processMsgMap)){
		std::invoke(it->second, msg);
	}
}

void PluginsManager::send(QJsonObject const& msg) {
	if(m_mediator)
		m_mediator->push(msg, PluginsManager::Identity);
}

void PluginsManager::send(QJsonObject const& msg, QStringView to) {
	if (m_mediator) {
		m_mediator->push(msg, PluginsManager::Identity, to);
	}
}

void PluginsManager::setMediator(AbstruactEventMediator* mediator) {
	if (m_mediator != mediator) {
		m_mediator = mediator;
	}
}

QStringView PluginsManager::identity() const {
	return PluginsManager::Identity;
}

void PluginsManager::load(QJsonArray const& arr) {
	using make_func_type = Plugin* (*)(PluginAction*);
	for (auto it = std::cbegin(arr); it != std::cend(arr); it = std::next(it)) {
		auto&& obj = it->toObject();
		auto&& name = obj["name"].toString();
		auto&& fileName = obj["fileName"].toString();
		auto&& isAsync = obj["async"].toBool();
		auto&& asyncGroup = obj["asyncGroup"].toString();
		auto&& regProperty = obj["regProperty"].toString();
		auto&& cfgFile = obj["cfgFile"].toString();

		make_func_type fun = (make_func_type)QLibrary::resolve(QDir::currentPath() + "/Plugins/" + fileName, "make_plugin");
		if (!fun) {
			std::string str = std::format("{} plugins is load failed, fileName is {}", name.toStdString(), fileName.toStdString());
			std::throw_with_nested(std::logic_error(str.c_str()));
		}
		Plugin* p = fun(this);
		if (!p) {
			std::string str = std::format("{} plugins is load succeed, but create is failed, fileName is {}", name.toStdString(), fileName.toStdString());
			std::throw_with_nested(std::logic_error(str.c_str()));
		}
		if (p->name() != name) {
			std::string str = std::format("{} plugins name is difference name in config file, load error {}", name.toStdString(), fileName.toStdString());
			std::throw_with_nested(std::logic_error(str.c_str()));
		}
		connect(p, &Plugin::loadFinished, this, &PluginsManager::pluginsLoadFinished);
		connect(p, &Plugin::errors, this, &PluginsManager::plugsErrorHandle);
		if (isAsync) {
			loadAsync(asyncGroup, name, cfgFile, p);
		}
		else {
			loadSync(regProperty, name, cfgFile, p);
		}
		auto ts = p->subTopic();
		auto cs = p->commands();
		std::for_each(std::cbegin(ts), std::cend(ts), [p, this](auto const& s) { m_topic2Plugins.emplace(s, p); });
		std::for_each(std::cbegin(cs), std::cend(cs), [p, this](auto const& s) { m_command2Plugins.emplace(s, p); });
	}
}

void PluginsManager::operate(QVariant v) {
	auto const&& obj = v.toJsonObject();
	auto&& [b, e] = m_commandMsgMap.equal_range(obj[ME_MSG_KEY].toString());
	if (b != std::cend(m_commandMsgMap)) {
		for (auto it = b; it != e; it = std::next(it)) {
			auto const&& msgObj = obj["message"].toObject();
			std::invoke(it->second, msgObj);
		}
	}	
	else {
		auto&& s = v.toString();
		if (!s.isEmpty()) {
			m_mediator->reportLog(s, 0);
		}
		else {
			qDebug() << "Plugins message param: " << v;
		}
	}
}

void PluginsManager::updateRegister(QJsonObject const& msgObj) {
	auto&& valueObj = msgObj["values"].toObject();
	auto&& [b, e] = m_topic2Plugins.equal_range(m_ownTopics.at(1));
	if (!msgObj["type"].isNull()) {
		QStringView simActionStr = u"SimAction";
		for (auto it = b; it != e; it++) {
			if (it->second->subTopic().contains("recv:" + simActionStr.toString())) {
				QMetaObject::invokeMethod(it->second, [p = it->second, valueObj] { std::invoke(&Plugin::sensorsEvent, p, valueObj); });
			}
		}
	}
	else {
		for (auto it = b; it != e; it = std::next(it)) {
			QMetaObject::invokeMethod(it->second, [p = it->second, valueObj] { std::invoke(&Plugin::sensorsEvent, p, valueObj); });
		}
	}
}

inline void PluginsManager::loadAsync(QStringView group, QStringView name, QStringView cfgFile, Plugin * const p) {
	auto it = m_asyncThreads.find(group.toString());
	QThread* th = nullptr;
	if (it == std::cend(m_asyncThreads)) {
		th = new QThread;
		m_asyncThreads[group.toString()] = th;
	}
	else {
		th = it->second;
	}
	p->moveToThread(th);
	th->start();
	QMetaObject::invokeMethod(p, [p, f = std::move(cfgFile.toString()), this] {
		if (!p->load(f)) {
			qDebug() << QStringLiteral("plugins initialization is failed!");
			auto it = m_pluginsMap.find(p->name().toString());
			if (it != std::cend(m_pluginsMap)) {
				m_pluginsMap.erase(it);
			}
			emit p->errors(p->name().toString(), QStringLiteral("plugins initialization is failed!"));
		}
	});
	m_pluginsMap[name.toString()] = p;
	connect(th, &QThread::finished, [p, this] {
		auto it = m_pluginsMap.find(p->name().toString());
		if (it != std::cend(m_pluginsMap)) {
			m_pluginsMap.erase(it);
		}
	});
	qDebug() << name.toString() + " plugins is load success as async";
	m_mediator->reportLog(name.toString() + " plugins is load success as async", 0);
}

void PluginsManager::loadSync(QStringView propertyName, QStringView name, QStringView cfgFile, Plugin* const p) {
	if (!p->load(cfgFile.toString())) {
		plugsErrorHandle(p->name().toString(), QStringLiteral("plugins initialization is failed!"));
		return;
	}
	m_pluginsMap[name.toString()] = p;
	p->setParent(this);
	if (!propertyName.isEmpty()) {
		m_context->setContextProperty(propertyName.toString(), p);
	}
	qDebug() << name.toString() + " plugins is load success as sync";
	m_mediator->reportLog(name.toString() + " plugins is load success as sync", 0);
}

void PluginsManager::plugsErrorHandle(QStringView text, QStringView name) {
	if (!m_isIntegrity) {
		qDebug() << name.toString() + QString(" plugins is error: ") + text.toString();
		emit errors(name.toString() + QString(" plugins is error: ") + text.toString());
	}
	else {
		qDebug() << name.toString() + QString(" plugins is error: ") + text.toString();
		m_mediator->reportLog(name.toString() + QString(" plugins is error: ") + text.toString(), 1);
	}
}

void PluginsManager::pluginsLoadFinished(QStringView name) {
	m_loadFinishedList << name.toString();
	if (m_loadFinishedList.size() == m_pluginsMap.size()) {		
		for (auto it = std::cbegin(m_loadFinishedList); it != std::cend(m_loadFinishedList); it = std::next(it)) {
			if (m_pluginsMap.find(*it) == std::end(m_pluginsMap)) {
				std::rethrow_exception(std::make_exception_ptr(std::exception("plugins load finished name errors")));
			}
		}
		if (!m_loadFinishedList.contains("QmlAttributes")) {
			std::rethrow_exception(std::make_exception_ptr(std::exception("plusgins must be contains QmlAttributes")));
		}
		//TODO 需要再次验证needCommands是否全部已经包含
		// 验证必须包含的插件
		//
		//********************************************
		m_isIntegrity = true;
		m_mediator->reportLog(QStringView(u"功能库加载完成"), 0);
		qDebug() << QStringView(u"All plugins load finished!");
		emit loadFinished();
	}
	else {
		qDebug() << name.toString() + QString(" plugins load finished!");
	}
}

void PluginsManager::logsHandle(QString const& logs, int lev) {
	QString text = logs;
	QJsonObject obj;
	obj["date_time"] = QDateTime::currentDateTime().addMSecs(std::rand() % 50).toString("yyyy-MM-dd hh:mm:ss.zzz");
	obj["level"] = lev;
	obj["logs"] = text.replace("\n", "\t");
	if (!m_isIntegrity) {
		QTimer::singleShot(3000, this, [obj = std::move(obj), this] {
			auto&& [b, e] = m_topic2Plugins.equal_range(m_ownTopics.at(0));
			for (auto it = b; it != e; it = std::next(it)) {
				QMetaObject::invokeMethod(it->second, [p = it->second, obj] { std::invoke(&Plugin::logsEvent, p, obj);});
			}
		});
	}
	else {
		auto&& [b, e] = m_topic2Plugins.equal_range(m_ownTopics.at(0));
		for (auto it = b; it != e; it = std::next(it)) {
			QMetaObject::invokeMethod(it->second, [p = it->second, obj] { std::invoke(&Plugin::logsEvent, p, obj); });
		}
	}
}
